/* Soubor je ulozen v kodovani UTF-8.
 * Kontrola kódování: Příliš žluťoučký kůň úpěl ďábelské ódy.
 */
package cz.pecinovsky.english.pojj.gui_obr;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Point;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Writer;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToggleButton;


/*******************************************************************************
 * Instance třídy GUI_OBR_4 přestavují grafické aplikace pro testování práce
 * p grafikou, obrázky a posluchači událostí.
 * Tato verze ukazuje všechny obrázky v zadané složce a
 * umožňuje zadat v nabídce ukládání žurnálu.
 * Přidává navíc možnost tisknout žurnál do panelu v aplikačním okně.
 *
 * @author    Rudolf PECINOVSKÝ
 * @version   0.00.000,  0.0.2006
 */
public class GUI_OBR_4
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Souřadnice, na které se aplikační okno přesune po spuštění GUI.
     *  To má smysl především při zobrazování na sekundárním monitoru. */
    private static final Point POINT_0 = new Point(0, 0);       //(-600, 0);

    /** Název "prázdného" obrázku - začíná a končí mezerou. Protože Windows
     *  název souboru začínající mezerou normálně nepřipustí, je tím do značné
     *  míry zabezpečena nemožnost kolize p jménem existujícího souboru. */
    private static final String NIC = " NIC ";

    /** Formátování časového údaje - očekává jediný parametr p datem+časem. */
    private static final String FORMÁT_ČASU =
        "%1$ty-%1$tm-%1$td - %1$tH:%1$tM:%1$tS,%1$tL - ";

    /** Množina přípon označujících formáty grafických souborů,
     *  který umí java nakreslit. */
    private static final Set<String> přípony = new HashSet<String>();



//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================

    /** Název složky, ve které jsou uloženy všechny soubory p obrázky.
     *  Tento název (a tím i příslušnou složku) je možné změnit
     *  na název zadaný jako parametr příkazového řádku. */
    private static String názevSložkyZdrojů;



//== STATICKÝ INICIALIZAČNÍ BLOK - STATICKÝ KONSTRUKTOR ========================

    static {
        try {
            názevSložkyZdrojů =
                new File(GUI_OBR_4.class.getResource("OBR").toURI()).
                         getCanonicalPath() + "/";
        }catch(Exception ex) {
            throw new RuntimeException(
                    "Nepodařilo se připravit složky se zdroji",ex);
        }

    /** Protože mapa přípon je statickým atributem, je nutno ji naplnit
     *  statickou metodou v deklaraci nebo
     *  ve statickém inicializačním bloku - to je použito zde. */
        přípony.add("png");
        přípony.add("gif");
        přípony.add("jpg");
        přípony.add("jpeg");
    }



//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

    /** Aplikační okno. */
    private final JFrame frmOkno =
            new JFrame("Práce se soubory a obrázky - verze 4");



//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    //Následující atributy jsou sice také konstantní,
    //ale budu-li je daklarovat jako proměnné,
    //nebudu je muset všechny inicializovat v konstruktoru

    private JMenuBar  mnbNabídka;   //Hlavní nabídka
    private JLabel    lblObrázek;   //Návěští zobrazující vykreslovaný obrázek

    private boolean   boolŽurnál;   //Příznak toho, má-li se vytvářet žurnál
    private boolean   boolŽPanel;   //Příznak záznamu žurnálu do panelu
    private boolean   boolŽSoubor;  //Příznak záznamu žurnálu do souboru

    private File        žSoubor;    //Soubor, do nějž se má ukládat žurnál
    private Writer      žVýstup;    //Výstupní proud ukládající žurnál
    private JTextArea   txaŽurnál;  //Textové pole, kam se má zapisovat žurnál
    private JScrollPane scpŽurnál;  //Skrolovaný panel p polem pro žurnál

    //Mapa obrázků a s nimi spojených ovládacích prvků indexovaná jejich názvy
    private Map<String, Přepravka> přepravky =
                                   new LinkedHashMap<String, Přepravka>();

    //Společný posluchač pro všechny přepínané ovládací prvky
    private ActionListener společnýPosluchač = new SpolečnýPosluchač();

    //"Grupy" jednotlivých přepínacích ovládacích prvků
    //z nichž může být v daném okamžiku nastaven vždy jen jediný
    private ButtonGroup
            bgrPovely   = new ButtonGroup(),    //Přepínač v nabídce
            bgrPřepnutí = new ButtonGroup(),    //Stavy přepínače
            bgrTlačítka = new ButtonGroup();    //Přepínací talčítka

    //Rozbalovací seznam poskytující další možnost zadání obrázku
    private JComboBox comboBox = new JComboBox();



//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ NESOUKROMÉ METODY TŘÍDY ===========================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vytvoří instanci aplikace, která ze zadané složky zjistí všechny
     * soubory se zobrazitelnými obrázky a připraví aplikační okno
     * umožňující zadat zobrazení obrázků různými způsoby.
     */
    private GUI_OBR_4()
    {
        //Následující posunutí má smysl pouze tehdy,
        //cheme-li aplikační okno zobrazit na sekundárním monitoru
        frmOkno.setLocation(POINT_0);

        //Nelze použí jednoduché nastavení ukončení aplikace při stisku
        //zavíracího tlačítka, protože součástí ukončení aplikace
        //musí být i zavření případného souboru se žurnálem.
        frmOkno.addWindowListener(new Zavírač());

        //Připraví přepravky p obrázky a jejich volícími prvky
        vytvořPřepravky(názevSložkyZdrojů);

        připravNabídku();   //Připraví hlavní nabídku

        panelComboBox();    //(Sever)   Přidá panel s rozbalovacím seznamem
        panelTlačítek();    //(Západ)   Přidá panel tlačítek
        panelPřepínačů();   //(Centrum) Přidá panel přepínače
        panelŽurnálu();     //(Východ)  Je rezervován pro žurnál
        panelObrázků();     //(Jih)     Vytvoří panel p obrázky

        //Simulace stisku tlačítka příslušného k prázdnému obrázku,
        //aby se přepínače inicializovaly
        JToggleButton btnNic = přepravky.get(NIC).tlačítko;

        //Volám: ActionEvent(Object source, int id, String command)
        ActionEvent událost  = new ActionEvent(btnNic, 1, btnNic.getText());
        společnýPosluchač.actionPerformed(událost);

        frmOkno.pack();             //Optimalizuje velikost vytvořeného okna
        frmOkno.setVisible(true);   //a zviditelní je
    }



//== ABSTRAKTNÍ METODY =========================================================
//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ INSTANCÍ =====================================
//== OSTATNÍ NESOUKROMÉ METODY INSTANCÍ ========================================
//== SOUKROMÉ A POMOCNÉ METODY TŘÍDY ===========================================

    /***************************************************************************
     * Vrátí vektor všech souborů, které se nachází v zadané složce
     * a mají některou z přípon zadaných v atributu {@code přípony}.
     *
     * @param složka Složka, v níž se budou hledat soubory
     * @return Vektor zobrazitelných souborů v zadané složce
     */
    private static File[] zjistiSoubory(File složka)
    {
        File[] ret = složka.listFiles(new FilenameFilter() {
            //Filtr, jímž projdou pouze soubory s některou z přípon,
            //zadaných v atributu přípony, tj. soubory, které bude program
            //umět nakreslit jako obrázky
            @Override
            public boolean accept(File dir, String name) {
                String přípona = name.substring(name.lastIndexOf('.')+1)
                                     .toLowerCase();
                //Akceptujeme pouze soubory s příponami ze zadané množiny
                return přípony.contains(přípona);
            }
        });
        //Kontrolní tisk názvů nalezených souborů
        System.out.println("Nalezené soubory s obrázky:");
        for(File f : ret) {
            System.out.println("   " + f);
        }
        return ret;
    }


//== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ========================================

    /***************************************************************************
     * Připraví v aplikačním okně hlavní nabídku
     */
    private void připravNabídku()
    {
        mnbNabídka = new JMenuBar();    //Vytvoření prázdné nabídkové lišty
        připravNabídkuSoubor();         //Přidání požadovaných nabídek
        připravNabídkuObrázky();
        připravNabídkuNápověda();
        frmOkno.setJMenuBar(mnbNabídka); //Přidání nabídkové lišty do okna
    }


    /***************************************************************************
     * Vytvoří rozbalovací seznam p názvy obrázků určených k zobrazení
     * a umístí jej do severního panelu aplikačního okna.
     */
    private void panelComboBox()
    {
        for(Přepravka p : přepravky.values())  {
            comboBox.addItem("S: " + p.název);
        }
        comboBox.addActionListener(společnýPosluchač);
        frmOkno.add(comboBox, BorderLayout.NORTH);
    }


    /***************************************************************************
     * Připraví do jižního panelu aplikačního okna panel,
     * v němž se budou zobrazovat jednotlivé obrázky.
     */
    private void panelObrázků()
    {
        //Zavádíme pro obrázek vlastní panel, aby byl malé obrázky vycentrovány
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        lblObrázek = new JLabel();      //Obrázek bude uložen v návěští
        panel.add(lblObrázek);
        frmOkno .add(panel, BorderLayout.SOUTH);
    }


    /***************************************************************************
     * Vytvoří přepínač, jehož polohy budou odpovídat jednotlivým obrázkům,
     * a umístí jej do centrálního panelu aplikačního okna.
     */
    private void panelPřepínačů()
    {
        Box bpanel = Box.createVerticalBox();
        for(Přepravka s : přepravky.values())  {
            bgrPřepnutí.add(s.přepnutí);  //Přidej do grupy stavů přepínače
            bpanel     .add(s.přepnutí);  //Přidej vlastní stav přepínače
        }
//        //Kdybychom použili následující příkaz, nebyl by přepínač
//        //při zvětšování aplikačního okna vycentrován ve své oblasti
//        frmOkno.add(bpanel, BorderLayout.CENTER);

        //Vložením bloku p příkazem do dalšího panelu dosáhneme vycentrování
        JPanel jpanel = new JPanel(new FlowLayout(FlowLayout.CENTER));

        jpanel.add(bpanel);
        frmOkno.add(jpanel, BorderLayout.CENTER);
    }


    /***************************************************************************
     * Připraví tlačítka, jejichž popisky budou odpovídat jednotlivým obrázkům,
     * a umístí je do západního panelu aplikačního okna.
     */
    private void panelTlačítek()
    {
        Box panel = Box.createVerticalBox();
        for(Přepravka s : přepravky.values())  {
            bgrTlačítka.add(s.tlačítko);     //Přidej do grupy tlačítek
            panel      .add(s.tlačítko);     //Přidej vlastní tlačítko
        }
        frmOkno.add(panel, BorderLayout.WEST);
    }


    /***************************************************************************
     */
    private void panelŽurnálu()
    {
        txaŽurnál = new JTextArea(12, 35);
        txaŽurnál.setEditable(false);
        scpŽurnál = new JScrollPane(txaŽurnál);
//        frmOkno.add(scpŽurnál, BorderLayout.EAST);
    }

    /***************************************************************************
     * Připraví nabídku Nápověda. Nabídka bude mít jednihou akci --
     * zobrazení nového, nemodálního okna se základními informacemi o aplikaci.
     * Tyto informace program získá ze soubor <b>Nápověda.html</b>,
     * uloženém ve stejné složce s kreslenými obrázky.
     */
    private void připravNabídkuNápověda()
    {
        JMenu     mnuNápověda;      //Položka v hlavní nabídce
        JMenuItem mniOAplikaci;     //Položka v rozbalované nabídce

        mnuNápověda = new JMenu("Nápověda");
        mnuNápověda.setMnemonic('N');
        mniOAplikaci = mnuNápověda.add("O aplikaci");

        mniOAplikaci.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                OknoNápovědy on = new OknoNápovědy();
                on.setSize(311, 200);
                on.setVisible(true);
            }
        });
        mnbNabídka.add(mnuNápověda);
    }


    /***************************************************************************
     * Připraví položku hlavní nabídky, jejíž povely budou odpovídat
     * jednotlivým obrázkům.
     */
    private void připravNabídkuObrázky()
    {
        JMenu mnuObrázky = new JMenu("Obrázky");   //Položka v hlavní nabídce
        for(Přepravka s : přepravky.values()) {
            JRadioButtonMenuItem rbmi = s.povel;
            bgrPovely .add(rbmi);     //Přidej do grupy povelů
            mnuObrázky.add(rbmi);     //Přidej jako položku nabídky
        }
        mnbNabídka.add(mnuObrázky);
    }


    /***************************************************************************
     * Připraví nabídku Soubor.
     */
    private void připravNabídkuSoubor()
    {
        JMenu mnuSoubor = new JMenu("Soubor");
        mnuSoubor.setMnemonic('S');
        mnbNabídka.add(mnuSoubor);

        JMenuItem mniOtevři = mnuSoubor.add("Otevři");
        mniOtevři.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                zadejSoubor();
            }
        });

        mnuSoubor.add(new ŽurnálSoubor());
        mnuSoubor.add(new ŽurnálPanel());
    }


    /***************************************************************************
     * Nechá zjistit zobrazitelné soubory v zadané složce a
     * ke každému z nich vytvoří přepravku obsahující ikonu obrázku
     * a ovládací prvky, které spustí její zobrazení:
     * tlačítko, položku seznamu, položku přepínače a položku hlavní nabídky.
     *
     * @param názevSložky Složka, v níž hledáme zobrazitelné soubory
     */
    private void vytvořPřepravky(String názevSložky)
    {
        File složka = new File(názevSložky);
        new Přepravka(); //Přepravka pro NIC
        File[] soubory = zjistiSoubory(složka);
        for(File soubor : soubory) {
            new Přepravka(soubor);
        }
    }


    /***************************************************************************
     * Požádá uživatele o zadání souboru pro žurnál.
     */
    private void zadejSoubor()
    {
        final JFileChooser fc = new JFileChooser();
        //Vynuluje žSoubor pro případ, že by uživatel nic nezadal
        žSoubor = null;
        int ret = fc.showOpenDialog(frmOkno);
        if (ret == JFileChooser.APPROVE_OPTION) {
            //Pokud uživatel něco zadal, zloží informaci do proměnné žSoubor
            žSoubor = fc.getSelectedFile();
        }
    }


    /***************************************************************************
     * Zavře výstupní soubor, do kterého se ukládá žurnál.
     */
    private void zavřiVýstup()
    {
        if(žVýstup != null) {
            try {
                žVýstup.close();
            } catch(IOException exc) {
                throw new RuntimeException(
                    "\nSoubor " + žVýstup +
                    " se žurnálem se nepodařilo zavřít", exc);
            }
        }
    }


    /***********************************************************************
     * Zaznamená další položku do žurnálu.
     *
     * @param ae      Zaznamenávaná dálost
     * @param zdroj0  Zdroj s prvním znakem označujícím druh ovládacího prvku
     * @param obrázek Název ukazovaného obrázku
     */
    private void zaznamenejDoŽurnálu(ActionEvent ae,
                                     String zdroj0, String obrázek)
    {
        String ovladač;
        switch(zdroj0.charAt(0)) {
            case 'N':   ovladač = "Nabídka  - ";   break;
            case 'P':   ovladač = "Přepínač - ";   break;
            case 'S':   ovladač = "Seznam   - ";   break;
            case 'T':   ovladač = "Tlačítko - ";   break;
            default:    ovladač = "### NEZNÁMÝ OVLADAČ ### - ";
        }
        String záznam = String.format(FORMÁT_ČASU + "%2$s%3$s\n",
                                       ae.getWhen(), ovladač, obrázek);
        if(boolŽPanel) {
            txaŽurnál.append(záznam);
        }
        if(boolŽSoubor) {
            try {
                žVýstup.write(záznam);
                žVýstup.flush();
            } catch(IOException exc) {
                throw new RuntimeException(
                    "\nChyba při výstupu do souboru " + žVýstup, exc);
            }
        }
    }



//== VNOŘENÉ A VNITŘNÍ TŘÍDY ===================================================

    /***********************************************************************
     * Instance třídy OknoNápovědy je dialogovým okněm,
     * které zobrazí obsah souboru, v němž se nachází nápověda.
     * Tento soubor ve v této aplikace zadání "natvrdo".
     */
    @SuppressWarnings("serial")
    private static class OknoNápovědy extends JDialog
    {
        private JEditorPane prohlížeč;

        OknoNápovědy()
        {
            prohlížeč = new JEditorPane();
            prohlížeč.setEditable(false);
            File nápověda = new File(názevSložkyZdrojů, "Nápověda.htm");
            try{
                 prohlížeč.setPage(nápověda.toURI().toURL());
            }
            catch (Exception e) {
                 JOptionPane.showMessageDialog(null,
                     "Nelze načíst soubor nápovědy " + nápověda + "\n" + e,
                     "Chyba", JOptionPane.ERROR_MESSAGE);
                 }
            getContentPane().add(prohlížeč);
        }
    }


    /***************************************************************************
     * Instance třídy Přepravka jsou jednoduché přepravky
     * sloužící ke združení souvisejících informací a objektů.
     * Třída musí být vnitřní, protože přihlašuje ke všem prvkům
     * společného posluchače, který je atributem instance.
     */
    private class Přepravka
    {
        final String               název;       //Název souboru s obrázkem
        final Icon                 obrázek;     //Vlastní obrázek
        final JToggleButton        tlačítko;    //Přepínací tlačíko
        final JRadioButtonMenuItem povel;       //Povel robalovací nabídky
        final JRadioButton         přepnutí;    //Stav přepínače


        /***********************************************************************
         * Vytvoří skupinu odpovídající počátečnímu prázdnému obrázku.
         */
        Přepravka() {
            this(NIC, new ImageIcon());
        }

        /***********************************************************************
         * Vytvoří skupinu odpovídající obrázku uloženému v zadaném souboru.
         * @param  soubor  Soubor p obrázkem
         */
        Přepravka(File soubor) {
            this(soubor.getName().
                         substring(0, soubor.getName().lastIndexOf('.')),
                  new ImageIcon(soubor.getAbsolutePath())
            );
        }

        /***********************************************************************
         * Vytvoří novou skupinu se zadaným názvem a obrázkem.
         * K této dvojici vytvoří všechny potřebné ovládací prvky
         * pro sspuštění zobrazení daného obrázku.
         *
         * @param  název    Název obrázku
         * @param  obrázek  Obrázek určený k zobrazování
         */
        private Přepravka(String název, Icon obrázek)
        {
            this.název   = název;
            this.obrázek = obrázek;

            tlačítko = new JToggleButton("T: " + název);
            tlačítko.addActionListener(společnýPosluchač);

            povel = new JRadioButtonMenuItem("N: " + název);
            povel.addActionListener(společnýPosluchač);

            přepnutí = new JRadioButton("P: " + název);
            přepnutí.addActionListener(společnýPosluchač);

            přepravky.put(název, this);
        }
    }


    /***************************************************************************
     * Instance třídy SpolečnýPosluchač představuje společného posluchače
     * všech prvků ovládajících zobrazování obrázků.
     */
    private class SpolečnýPosluchač implements ActionListener
    {
        /***********************************************************************
         * {@inheritDoc}
         * @param ae
         */
        @Override
        public void actionPerformed(ActionEvent ae)
        {
            String zdroj0 = ae.getActionCommand();  //Zjisti původce

            //S combo-boxem se musí zacházet nestandardně
            if(zdroj0.equals("comboBoxChanged")) {
                zdroj0 = (String)comboBox.getSelectedItem();
            }
            String zdroj = zdroj0.substring(3);   //Z popisku odvodí název

            //Najde odpovídající skupinu = obrázek
            if(!přepravky.containsKey(zdroj)) {
                throw new RuntimeException("Neznámá událost");
            }
            Přepravka skupina = přepravky.get(zdroj);
            lblObrázek.setIcon(skupina.obrázek);

            //Vysvětlí všem grupám, který prvek mají nastavit jako "stisknutý"
            skupina.povel   .getModel().setSelected(true);
            skupina.přepnutí.getModel().setSelected(true);
            skupina.tlačítko.getModel().setSelected(true);

            //Aby nastavování stavu combo-boxu nevylolalo událost,
            //musí se mu na dobu nuceného nastavení nového stavu
            //odebrat posluchač a po nastavení zase vrátit
            comboBox.removeActionListener(this);
            comboBox.getModel().setSelectedItem(zdroj);
            comboBox.addActionListener(this);

            if(boolŽurnál) {
                zaznamenejDoŽurnálu(ae, zdroj0, zdroj);
            }
            frmOkno.pack();
            lblObrázek.repaint();
        }
     }


    /***************************************************************************
     * Instance třídy {@code Zavírač} je posluchačem událostí aplikačního okna
     * čekajícím pouze na povel pro zavření okna, při němž zabezpečí uložení
     * případného otevřeného souboru žurnálu a následné ukončení aplikace.
     */
    private class Zavírač extends WindowAdapter
    {
         /** {@inheritDoc} */
        @Override
        public void windowClosing(WindowEvent e) {
            if(žVýstup != null)  {
                try { žVýstup.write(
                          String.format("##### " + FORMÁT_ČASU +
                                         "Program ukončen\n\n",
                                         System.currentTimeMillis()));
                } catch(IOException exc) {
                    throw new RuntimeException(
                        "\nNepodařilo se zapsat závěrečnou zprávu",exc);
                }
                zavřiVýstup();
            }
            System.exit(0);
        }
    }


    /***************************************************************************
     * Společných rodič obou tříd zaškrtávacích položek nabídky
     * ovlivňujících záznam akcí do žurnálu.
     */
    private abstract class ŽurnálAbstractRodič extends    JCheckBoxMenuItem
                                               implements ItemListener
    {
        /***********************************************************************
         * Vytvoří položku nabídky zadaného jména.
         *
         * @param název Název vytvářené položky
         */
        ŽurnálAbstractRodič(String název)
        {
            super(název);
        }

        /***********************************************************************
         * Připraví startovací text označující zahájení žurnálu.
         */
        String start()
        {
            return String.format(
                "\n===== " + FORMÁT_ČASU + "Zahájení záznamu do žurnálu\n",
                System.currentTimeMillis());
        }

        /***********************************************************************
         * Připraví závěrečný text označující ukončení žurnálu.
         */
        String stop()
        {
            return String.format(
                "===== " + FORMÁT_ČASU + "Ukončení záznamu do žurnálu\n",
                System.currentTimeMillis());
        }
    }


    /***************************************************************************
     * Instance třídy představuje položku ovlivňující zápiš žurnálu
     * do panelu, který je součáístí aplikačního okna.
     */
    @SuppressWarnings("serial")
    private class ŽurnálPanel extends ŽurnálAbstractRodič
    {
        /***********************************************************************
         * Vytvoří zaškrtávací políčko jako položku nabídky
         * a přihlísí se jako vlastní posluchač.
         */
        ŽurnálPanel()
        {
            super("Žurnál do panelu");
            addItemListener(this);
        }


        /***********************************************************************
         * {@inheritDoc}
         * @param ie Ošetřovaná událost
         */
        public void itemStateChanged(ItemEvent ie)
        {
            if(getState()) {
                frmOkno.add(scpŽurnál, BorderLayout.EAST);
                boolŽurnál = boolŽPanel = true;
                txaŽurnál.append(start());
            } else {
                boolŽPanel = false;
                boolŽurnál = boolŽSoubor;
                txaŽurnál.append(stop());
                frmOkno.remove(scpŽurnál);
            }
            frmOkno.pack();
        }
    }


    /***************************************************************************
     * Instance třídy představuje položku ovlivňující zápiš žurnálu
     * do do souboru.
     */
    @SuppressWarnings("serial")
    private class ŽurnálSoubor extends ŽurnálAbstractRodič
    {
        /***********************************************************************
         * Vytvoří zaškrtávací políčko jako položku nabídky
         * a přihlísí se jako vlastní posluchač.
         */
        ŽurnálSoubor()
        {
            super("Žurnál do souboru");
            addItemListener(this);
        }


        /***********************************************************************
         * {@inheritDoc}
         * @param ie Ošetřovaná událost
         */
        public void itemStateChanged(ItemEvent ie)
        {
            if(getState()) {
                if(žSoubor == null)  {
                    zadejSoubor();
                }
                if(žSoubor == null) {  //Soubor nebyl zadán
                    setState(boolŽSoubor = false);
                    boolŽurnál = boolŽPanel;
                }
                else {
                    try {
                        žVýstup = new FileWriter(žSoubor, true);
                        žVýstup.write(start());
                        žVýstup.flush();
                        boolŽurnál = boolŽSoubor = true;
                    } catch(IOException exc) {
                        throw new RuntimeException(
                            "\nNepodařilo se otevřít soubor", exc);
                    }
                }
            } else {
                boolŽSoubor = false;
                boolŽurnál  = boolŽPanel;
                try{
                    žVýstup.write(stop());
                } catch(IOException exc){
                    throw new RuntimeException(
                        "\nNepodařilo se zapsat ukončení žurnálu", exc);
                }
                zavřiVýstup();
            }
        }
    }


//== TESTY A METODA MAIN =======================================================

    /***************************************************************************
     * Testovací metoda.
     * @param args  Parametry příkazového řádku p názvem složky,
     *              v níž jsou umístěny soubory p obrázky
     */
     public static void test(String[] args) {
         if(args.length > 0) {
             názevSložkyZdrojů = args[0];
         }
         new GUI_OBR_4();
     }
    /***************************************************************************
     * @param args  Parametry příkazového řádku p názvem složky,
     *              v níž jsou umístěny soubory p obrázky
     */
     public static void main(String[] args)  {  test(args);  }
     /*-*/
}
